package com.wocao.config;

import com.wocao.entity.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.sql.DataSource;
import java.util.Arrays;

/**
 * SpringSecurity整合MVC配置，继承WebSecurityConfigurerAdapter
 * Adapter适配器设计模式，实现接口必须实现接口中所有方法
 * Adapter给接口方法默认实现，继承Adapter后只需要实现特定方法
 */
//启动注解配置授权访问
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //提供密码编码器给容器
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private PersistentTokenRepository persistentTokenRepository;

    //配置验证的账号密码
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //配置自定义的登录逻辑
        auth.userDetailsService(userDetailsService);
    }

    //放行图片上传方法
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/**/upload/*",
                "/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs","/uni-app/upload",
                "/uni-app/appLogin", "/uni-app/addRegister", "/uni-app/sugUploadImages",
                "/uni-app/zyComplaintSuggest/sugUploadImages","/uni-app");
    }

    //配置访问授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置资源的授权访问
        http.authorizeRequests().antMatchers("/login", "/logout").permitAll() //上面地址放行
                .anyRequest().authenticated()               //其它资源需要登录验证
                .and()
                .formLogin().loginProcessingUrl("/login")
                .successHandler(new LoginSuccessHandler())      //配置登录成功处理器
                .failureHandler((request, response, exception) -> {   //配置登录失败处理器
//                    //向前端发送错误数据
//                    ResponseResult.write(ResponseResult.error(401L, "账号或密码错误"), response);
                    if ("账号不存在".equals(exception.getMessage())) {
                        // 账号不存在的错误信息
                        ResponseResult.write(ResponseResult.error(400L, "账号不存在"), response);
                    } else if ("账号已被停用".equals(exception.getMessage())) {
                        // 账号已被停用的错误信息
                        ResponseResult.write(ResponseResult.error(401L, "账号已被停用"), response);
                    } else {
                        //打印错误日志
                        exception.printStackTrace();
                        // 其他错误信息
                        ResponseResult.write(ResponseResult.error(401L, "账号或密码错误"), response);
                    }
                })
                .and()
                .exceptionHandling()
                .authenticationEntryPoint((request, response, chain) -> {   //配置权限认证失败处理器
                    //向前端发送错误数据
                    ResponseResult.write(ResponseResult.error(403L, "用户没有权限"), response);
                })
                .and()
                .logout()
                .logoutSuccessHandler((request, response, chain) -> {   //配置注销成功处理器
                    //向前端发送数据
                    ResponseResult.write(ResponseResult.ok("注销成功"), response);
                })
                .clearAuthentication(true)      //清除服务器验证信息
                .and()
                .csrf().disable()               //停止csrf攻击保护,方便测试，上线再开启
                .sessionManagement()            //session管理
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .cors()
                //配置跨域
                .configurationSource(corsConfigurationSource())
                .and()          //记住我配置
                .rememberMe()
                .tokenRepository(persistentTokenRepository) //数据源
                .rememberMeParameter("rememberMe")
//                .userDetailsService(userDetailsService)
                .tokenValiditySeconds(7 * 24 * 3600) //时间 秒
                .and()                         //在过滤器链里添加自定义验证过滤器
                .addFilter(new TokenAuthenticationFilter(authenticationManager()));
    }

    /**
     * 跨域配置对象
     *
     * @return
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        //配置允许访问的服务器域名
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    /**
     * 配置记住我的数据源
     *
     * @return
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        //使用jdbc实现记住我
        JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
        //配置数据源
        repository.setDataSource(dataSource);
        //第一次启动时创建表，后面不能创建
//        repository.setCreateTableOnStartup(true);
        return repository;
    }

}
